Add an exclude key to the manifest
authorAlex Crichton <alex@alexcrichton.com>
Sat, 16 Aug 2014 00:57:16 +0000 (17:57 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Tue, 19 Aug 2014 06:43:27 +0000 (23:43 -0700)
This key is used to help determine the set of input files for a package. The
normal method (via walking or git ls-files) is filtered via all the patterns
provided in `exclude` of the project section.

The toml key is a string array corresponding to a set of glob patterns according
to the syntax of libglob (provided in the standard distribution). The set of
files normally found will be filtered by each pattern, and if any pattern
matches then the path is excluded.

This will later on be used for `cargo package` when generating a tarball to get
uploaded.

src/cargo/core/manifest.rs
src/cargo/lib.rs
src/cargo/sources/path.rs
src/cargo/util/toml.rs
tests/test_cargo_compile.rs

index 2d7eedd70deef773dbd7de2fc963f7afeba15513..6d7fe2dd2360f161c19ad93d889946f1e8db9e7c 100644 (file)
@@ -24,6 +24,7 @@ pub struct Manifest {
     sources: Vec<SourceId>,
     build: Vec<String>,
     warnings: Vec<String>,
+    exclude: Vec<String>,
 }
 
 impl Show for Manifest {
@@ -312,7 +313,7 @@ impl Show for Target {
 impl Manifest {
     pub fn new(summary: &Summary, targets: &[Target],
                target_dir: &Path, doc_dir: &Path, sources: Vec<SourceId>,
-               build: Vec<String>) -> Manifest {
+               build: Vec<String>, exclude: Vec<String>) -> Manifest {
         Manifest {
             summary: summary.clone(),
             authors: Vec::new(),
@@ -322,6 +323,7 @@ impl Manifest {
             sources: sources,
             build: build,
             warnings: Vec::new(),
+            exclude: exclude,
         }
     }
 
@@ -376,6 +378,10 @@ impl Manifest {
     pub fn get_warnings(&self) -> &[String] {
         self.warnings.as_slice()
     }
+
+    pub fn get_exclude(&self) -> &[String] {
+        self.exclude.as_slice()
+    }
 }
 
 impl Target {
index f701553d302c78beb46728bc37aa76fb7e7e730a..ba092de4b51caf18b76810108cf6952f83e7938b 100644 (file)
@@ -7,6 +7,7 @@
 
 extern crate collections;
 extern crate debug;
+extern crate glob;
 extern crate regex;
 extern crate semver;
 extern crate serialize;
index c7d0e7d5130ed87a049270364ff3ee67f55fa768..0d65a19c725c363dad952c2a936c29384044fc9d 100644 (file)
@@ -2,6 +2,7 @@ use std::cmp;
 use std::fmt::{Show, Formatter};
 use std::fmt;
 use std::io::fs;
+use glob::Pattern;
 
 use core::{Package, PackageId, Summary, SourceId, Source, Dependency, Registry};
 use ops;
@@ -68,61 +69,69 @@ impl PathSource {
     /// are relevant for building this package, but it also contains logic to
     /// use other methods like .gitignore to filter the list of files.
     pub fn list_files(&self, pkg: &Package) -> CargoResult<Vec<Path>> {
-        // TODO: add an `excludes` section to the manifest which is another way
-        // to filter files out of this set that is returned.
-        return if self.path.join(".git").exists() {
+        let candidates = try!(if self.path.join(".git").exists() {
             self.list_files_git(pkg)
         } else {
             self.list_files_walk(pkg)
-        };
-
-        fn list_files_git(&self, pkg: &Package) -> CargoResult<Vec<Path>> {
-            let cwd = pkg.get_manifest_path().dir_path();
-            let mut cmd = process("git").cwd(cwd.clone());
-            cmd = cmd.arg("ls-files").arg("-z");
-
-            // Filter out all other packages with a filter directive
-            for pkg in self.packages.iter().filter(|p| *p != pkg) {
-                if cwd.is_ancestor_of(pkg.get_manifest_path()) {
-                    let filter = pkg.get_manifest_path().dir_path()
-                                    .path_relative_from(&self.path).unwrap();
-                    cmd = cmd.arg("-x").arg(filter);
-                }
+        });
+
+        let pats = pkg.get_manifest().get_exclude().iter().map(|p| {
+            Pattern::new(p.as_slice())
+        }).collect::<Vec<Pattern>>();
+
+        let root = pkg.get_manifest_path().dir_path();
+        Ok(candidates.move_iter().filter(|candidate| {
+            let candidate = candidate.path_relative_from(&root).unwrap();
+            !pats.iter().any(|p| p.matches_path(&candidate))
+        }).collect())
+    }
+
+    fn list_files_git(&self, pkg: &Package) -> CargoResult<Vec<Path>> {
+        let cwd = pkg.get_manifest_path().dir_path();
+        let mut cmd = process("git").cwd(cwd.clone());
+        cmd = cmd.arg("ls-files").arg("-z");
+
+        // Filter out all other packages with a filter directive
+        for pkg in self.packages.iter().filter(|p| *p != pkg) {
+            if cwd.is_ancestor_of(pkg.get_manifest_path()) {
+                let filter = pkg.get_manifest_path().dir_path()
+                                .path_relative_from(&self.path).unwrap();
+                cmd = cmd.arg("-x").arg(filter);
             }
+        }
 
-            log!(5, "listing git files with: {}", cmd);
-            let output = try!(cmd.arg(".").exec_with_output());
-            let output = output.output.as_slice();
-            Ok(output.split(|x| *x == 0).map(Path::new).collect())
+        log!(5, "listing git files with: {}", cmd);
+        let output = try!(cmd.arg(".").exec_with_output());
+        let output = output.output.as_slice();
+        Ok(output.split(|x| *x == 0).map(|p| cwd.join(p)).collect())
+    }
+
+    fn list_files_walk(&self, pkg: &Package) -> CargoResult<Vec<Path>> {
+        let mut ret = Vec::new();
+        for pkg in self.packages.iter().filter(|p| *p == pkg) {
+            let loc = pkg.get_manifest_path().dir_path();
+            try!(walk(&loc, &mut ret, true));
         }
+        return Ok(ret);
 
-        fn list_files_walk(&self, pkg: &Package) -> CargoResult<Vec<Path>> {
-            let mut ret = Vec::new();
-            for pkg in self.packages.iter().filter(|p| *p == pkg) {
-                let loc = pkg.get_manifest_path().dir_path();
-                try!(walk(&loc, &mut ret, true));
+        fn walk(path: &Path, ret: &mut Vec<Path>,
+                is_root: bool) -> CargoResult<()> {
+            if !path.is_dir() {
+                ret.push(path.clone());
+                return Ok(())
             }
-            return Ok(ret);
-
-            fn walk(path: &Path, ret: &mut Vec<Path>,
-                    is_root: bool) -> CargoResult<()> {
-                if !path.is_dir() {
-                    ret.push(path.clone());
-                    return Ok(())
-                }
-                // Don't recurse into any sub-packages that we have
-                if !is_root && path.join("Cargo.toml").exists() { return Ok(()) }
-                for dir in try!(fs::readdir(path)).iter() {
-                    match (is_root, dir.filename_str()) {
-                        (_,    Some(".git")) |
-                        (true, Some("target")) |
-                        (true, Some("Cargo.lock")) => continue,
-                        _ => {}
-                    }
-                    try!(walk(dir, ret, false));
+            // Don't recurse into any sub-packages that we have
+            if !is_root && path.join("Cargo.toml").exists() { return Ok(()) }
+            for dir in try!(fs::readdir(path)).iter() {
+                match (is_root, dir.filename_str()) {
+                    (_,    Some(".git")) |
+                    (true, Some("target")) |
+                    (true, Some("Cargo.lock")) => continue,
+                    _ => {}
                 }
-                return Ok(())
+                try!(walk(dir, ret, false));
             }
+            return Ok(())
         }
     }
 }
index a5bf74c49f27e5c78e68a78459b93463e8f7f6c3..03445dccd8bbfe9617179f8857b54934e5abbbe9 100644 (file)
@@ -195,7 +195,7 @@ pub struct TomlManifest {
     test: Option<Vec<TomlTestTarget>>,
     bench: Option<Vec<TomlTestTarget>>,
     dependencies: Option<HashMap<String, TomlDependency>>,
-    dev_dependencies: Option<HashMap<String, TomlDependency>>
+    dev_dependencies: Option<HashMap<String, TomlDependency>>,
 }
 
 #[deriving(Encodable,Decodable,PartialEq,Clone)]
@@ -220,6 +220,7 @@ pub struct TomlProject {
     pub version: String,
     pub authors: Vec<String>,
     build: Option<TomlBuildCommandsList>,
+    exclude: Option<Vec<String>>,
 }
 
 #[deriving(Encodable,Decodable,PartialEq,Clone,Show)]
@@ -418,6 +419,7 @@ impl TomlManifest {
             Some(MultipleBuildCommands(ref cmd)) => cmd.clone(),
             None => Vec::new()
         };
+        let exclude = project.exclude.clone().unwrap_or(Vec::new());
 
         let summary = Summary::new(&pkgid, deps.as_slice());
         let mut manifest = Manifest::new(&summary,
@@ -425,7 +427,8 @@ impl TomlManifest {
                                          &layout.root.join("target"),
                                          &layout.root.join("doc"),
                                          sources,
-                                         build);
+                                         build,
+                                         exclude);
         if used_deprecated_lib {
             manifest.add_warning(format!("the [[lib]] section has been \
                                           deprecated in favor of [lib]"));
index 4931a6cf9993dc0adaa33377ec9e373de54e4a75..c5834741f158c30e5b8dcf614fcf4b02b86c9e58 100644 (file)
@@ -1,10 +1,11 @@
-use std::io::{fs, TempDir};
+use std::io::{fs, TempDir, File};
 use std::os;
 use std::path;
 
 use support::{ResultTest, project, execs, main_file, basic_bin_manifest};
-use support::{COMPILING, RUNNING, cargo_dir, ProjectBuilder, path2url};
+use support::{COMPILING, RUNNING, FRESH, cargo_dir, ProjectBuilder, path2url};
 use hamcrest::{assert_that, existing_file};
+use support::paths::PathExt;
 use cargo;
 use cargo::util::{process, realpath};
 
@@ -1486,3 +1487,41 @@ test!(deprecated_lib {
                        .with_stderr("\
 the [[lib]] section has been deprecated in favor of [lib]\n"));
 })
+
+test!(freshness_ignores_excluded {
+    let foo = project("foo")
+        .file("Cargo.toml", r#"
+            [package]
+            name = "foo"
+            version = "0.0.0"
+            authors = []
+            build = "true"
+            exclude = ["src/b*.rs"]
+        "#)
+        .file("src/lib.rs", "pub fn bar() -> int { 1 }");
+    foo.build();
+    foo.root().move_into_the_past().assert();
+
+    assert_that(foo.process(cargo_dir().join("cargo-build")),
+                execs().with_status(0)
+                       .with_stdout(format!("\
+{compiling} foo v0.0.0 ({url})
+", compiling = COMPILING, url = foo.url())));
+
+    // Smoke test to make sure it doesn't compile again
+    println!("first pass");
+    assert_that(foo.process(cargo_dir().join("cargo-build")),
+                execs().with_status(0)
+                       .with_stdout(format!("\
+{fresh} foo v0.0.0 ({url})
+", fresh = FRESH, url = foo.url())));
+
+    // Modify an ignored file and make sure we don't rebuild
+    println!("second pass");
+    File::create(&foo.root().join("src/bar.rs")).assert();
+    assert_that(foo.process(cargo_dir().join("cargo-build")),
+                execs().with_status(0)
+                       .with_stdout(format!("\
+{fresh} foo v0.0.0 ({url})
+", fresh = FRESH, url = foo.url())));
+})